import serial
import logging
import ConfigParser
import serial
import threading
import socket
import struct

import sys
sys.path.append("../db")
import rnetdb
from rnetdb import Device
#
# GLOBALS
#

config = ConfigParser.RawConfigParser()

serport = None
inproc = None
logger = None
srvsocks = []
clsocks = []

rnetUnits = None

#
# CLASSES
#

# Destroy handler #

class StopHandler:
    def handle(self, line):
        logger.warning("Shutting down requested by input handler")
        run_cleanup()
        return True

# Serial handler #

class SerialHandler:
    def handle(self, line):
        serport.write(line + '\n')
        return True

# RNET UNITS handler #

class RNETUnits:

    def __init__(self):
        self.refresh()

    def refresh(self):
        results = rnetdb.db.query("SELECT id, data_type, multiplier, offset FROM UNITS")
        self.units = dict()

        logger.info("RNET-Utils: Preloading unit datatypes")

        # Construct dictionary <unit> = <datatype>
        for x in results:
            logger.info("RNET-Units: Unit: %s DataType: %s", x.id, x.data_type)
            self.units[x.id] = [int(x.data_type), float(x.multiplier), float(x.offset)]

    def unpack_datatype(self,datatype,value):

        # UNSIGNED INTEGER #
        if datatype == 1:
            # unpack requires to pad with 0s
            value = value.ljust(8,'0')
            return struct.unpack("<I",value.decode("hex"))[0]

        # SINGED INTEGER #
        if datatype == 2:
            # unpack requires to pad with 0s
            value = value.ljust(8,'0')
            return struct.unpack("<i",value.decode("hex"))[0]

        # STRING %
        if datatype == 3:
            return value.decode("hex")

        return None

    def convert(self, unit, value):

        # find unit information
        entry = self.units.get(unit,None)
        if entry is None:
            logger.error("RNET: unknown datatype for unit %s",unit)
            return None

        # convert encoded value to data
        converted = self.unpack_datatype(entry[0],value)
        if converted is None:
            logger.error("RNET: datatype %s not found", entry[0])
            return None

        # integrate offset
        if entry[2] != 0.0:
            offset = entry[2]
            # if offset is integer
            if float(int(offset)) == offset:
                converted = converted + int(offset)
            else:   converted = float(converted) + offset

        # integrate multiplier
        if entry[1] != 1.0:
            mul = entry[1]
            if float(int(mul)) == mul:
                converted = converted * int(mul)
            else:   converted = float(converted) * mul

        return converted

# RNET Capabilities promote sub-handler #

class RNETHandler_CapPromote:

    def handle(self,args):
        # Get parameters
        try:
            address = int(args["ADDR"],16)
            capid = int(args["CID"],16)
            unit = int(args["U"],16)
            value = args["V"]
            endpoint = int(args["EP"],16)
        except:
            logger.error("RNET: invalid capability promote arguments: %s",args)
            return False

        # Log event
        logger.info("RNET: cap promote ADDR:%s CID:%s UNIT:%s VALUE:%s on EP:%s",
                    address, capid, unit, value, endpoint)

        try:

            # convert unit to readable form
            value = rnetUnits.convert(unit,value)
            if value == None:
                logger.error("RNET: failed converting unit on cap promote ADDR:%s CID:%s U:%s VALUE:%s",
                    address, capid, unit, value)
                return False

            # add to databse
            rnetdb.db.query(
                "INSERT INTO HISTORY_CAPS (address,endpoint,cap_id,unit,value)"
                " VALUES ('%s','%s','%s','%s','%s')" % (address, endpoint, capid, unit, value)
                )
        except:
            logger.exception("RNET: failed adding capabillity promote to database")
            return False

        return True

# RNET advertise secondary handler #

class RNETHandler_Advertise:

    def __init__(self):
        pass

    def handle(self,args):
         # Get parameters
        try:
            address = int(args["ADDR"],16)
            offline = int(args["OFF"])
            device_flags = args["DFL"]
            user_flags = args["USR"]
            endpoint = int(args["EP"],16)
        except:
            logger.error("RNET: invalid advertise promote arguments: %s", args)
            return False

        # Log event
        logger.info("RNET: device advertise ADDR:%s OFFLINE:%s DEVFL:%s USFL:%s on EP:%s",
                address, offline, device_flags, user_flags, endpoint)

        try:
            # add to databse
            rnetdb.db.query(
                "INSERT INTO HISTORY_ADVERTISES (address,endpoint,offline,devflags,userflags)"
                " VALUES ('%s','%s','%s','%s','%s')" % (address, endpoint, offline, device_flags, user_flags)
                )
        except:
            logger.exception("RNET: failed adding capability promote to database")
            return False

# RNET main handler #

class RNETHandler:

    def __init__(self):
        self.SubHandlers = {
            "CPP" : RNETHandler_CapPromote(),
            "ADV": RNETHandler_Advertise()
        }

    def handle(self,line):
        # Split args
        args = InputProcessor.extract_args(line)
        if args == None:
            return False

        operation = args.get("H")
        if operation == None:
            logger.error("RNET: no operation specified in args: %s",args)
            return False

        # Check operation (e.g.:CPP)
        subhandler = self.SubHandlers.get(operation)
        if subhandler == None:
            logger.error("RNET: unknown operation '%s'",operation)
            return False

        # Pass the job to the subhandler
        logger.info("RNET: running subhandler '%s'",operation)
        return subhandler.handle(args)


# RFHandler #

class RFHandler:
    def __init__(self):
        return

    def handle(self, line):
        # Split args
        args = InputProcessor.extract_args(line)
        if args == None:
            return False

        # TODO:

        return True


# InputProcessor #

class InputProcessor:
    def __init__(self):
        self.handlers = {}

    @staticmethod
    def extract_args(line):
        res = {}

         # Extract args passed in brackets (e.g. [])
        index = line.find('[')
        count = 0
        while index >= 0 :

            end = line.find(']',index+1)
            if end < 0 : return None   # error #

            param = line[index+1:end]
            if len(param) == 0: return None # error #

            # Store special parameter
            res["#" + str(count)] = param
            count += 1

            line = line[:index] + line[end+1:]
            index = line.find('[')

        # Extract all arguments that match format <arg>:<value>
        lst = line.split()
        for item in lst:
            params = item.split(':')
            if len(params) != 2: continue
            res[params[0]] = params[1]

        return res

    def input(self, text):

        # Split all lines
        lines = text.splitlines()

        for line in lines:
            self.input_line(line)

        return

    def input_line(self, line):
        # for each line of input
        logger.info("Input: '%s'", line)
        line = line.lstrip()

        if len(line) == 0:
            logger.info("Input: empty input")
            return

        # get handler name
        index = line.find(':')
        if index == -1:
            logger.warning("Input: invalid input: '%s'", line)
            return

        handler = line[:index]
        line = line[index + 1:]

        # find handler
        if self.handlers.has_key(handler) == False:
            logger.error("Input: no handler for: '%s'", handler)
            return

        # pass to handler
        self.handlers[handler].handle(line)
        return


# THREADS

def Thread_Serial():
    while True:
        line = serport.readline()
        inproc.input(line)

# IMPLEMENTATION

def start_serial_thread():
    global serport

    # Serial
    logger.info("Serial port='%s' Baud=%s",
                config.get("SERIAL", "port"),
                config.get("SERIAL", "baudrate"))

    try:
        serport = serial.Serial(
            config.get("SERIAL", "port"),
            config.get("SERIAL", "baudrate"))
    except:
        logger.exception("Failed: opening serial port '%s'", config.get("SERIAL", "port"))
        return False

    thread = threading.Thread(target=Thread_Serial)
    thread.start()

def config_defaults():
        config = ConfigParser.RawConfigParser()
        config.add_section("SERIAL")
        config.set("SERIAL", 'port', 'COM1')
        config.set("SERIAL", 'baudrate', 19200)
        config.set("SERIAL", 'timeout', 1)

        with open('processor_defaults.cfg', 'wb') as configfile:
            config.write(configfile)


def run_config():
    global config
    try:
        config.readfp(open('processor_defaults.cfg'))
    except:
        config_defaults()

    try:
        config.readfp(open("processor.cfg"))
    except:
        logger.exception("Failed: loading config file, using defaults")
        pass


def run_main():
    global logger, config, inproc, serport

    # Logging
    logging.basicConfig()
    logger = logging.getLogger()

    """ Debug """
    logger.setLevel(logging.DEBUG)

    # Configuration
    run_config()

    # Setup rnetdb
    rnetdb.setup_sqlite("../db/rnet.db")

    # Setup RNET units
    try:
        global rnetUnits
        rnetUnits = RNETUnits()
    except:
        logger.exception("Failed: setting up RNET units")
        exit()

    # Setup input
    inproc = InputProcessor()

    # Input handlers
    inproc.handlers.setdefault('RF', RFHandler())
    inproc.handlers.setdefault('ser', SerialHandler())
    inproc.handlers.setdefault('stop',StopHandler())
    inproc.handlers.setdefault('RN',RNETHandler())


def run_loop():
    line = serport.readline()
    logger.debug("Serial:" + line)
    inproc.input(line)


def run_cleanup():
    global serport
    logger.warning("Shutting down serial thread..")

    try:
        serport.close()
        serport = None
    except:
        logger.error("Failed, closing serial port")

    logger.warning("Shutting down TCP server..")

    try:
        global clsocks, srvsocks

        for conn in clsocks:
            conn.close()
        for sock in srvsocks:
            sock.close()

        # Clear lists
        clsocks = []
        srvsocks = []
    except:
        logger.error("Faiiled, closing sockets")
        raise

    return

# Main routine
if __name__ == "__main__":

    try:
        run_main()

        # Launch serial thread
        start_serial_thread()
    except:
        logging.exception("Failed to start processor!")
        run_cleanup()
        raise
